package main;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//Nikolas Wolfe
//3/18/09
//CIS 4930
//Game Of Life

// This does the Game Of Life computations

public class GameOfLife
{
	static private final int WHITE 	= 0xFFFFFFFF;
	static private final int RED 	= 0xFFFF0000; 
	static private final int GREEN 	= 0xFF00FF00;
	static private final int BLUE 	= 0xFF0000FF;
	
	private final int xMax;
	private final int yMax;
	
	private final FrameOfLife frame;
	private final PaneOfLife grid;
	private ExecutorService exec;
	private CyclicBarrier barrier;
	private ToroidalArray srcArray;
	private ToroidalArray destArray;
	private int NUM_THREADS;
	private int NUM_GENERATIONS;
	private Worker[] workers;
	
	public GameOfLife(FrameOfLife frameOfLife)
	{
		this.frame = frameOfLife;
		this.grid = frame.getPaneOfLife();
		this.xMax = grid.getWidth();
		this.yMax = grid.getHeight();
		this.srcArray = grid.getImageArray();
		this.destArray = new ToroidalArray(xMax, yMax);
	}
	
	//=========================================================================
	// start() -- play the Game Of Life
	
	public void start()
	{
		//initializeBarrier();
		runCyclicBarrier();
	}

	//=========================================================================
	// stop() -- stop the thread pool
	
	public void stop()
	{
		exec.shutdownNow();
	}
	
	//=========================================================================
	// initialize() -- used when a NEW simulation is to be run		
	
	public void initialize()
	{
		this.NUM_THREADS = frame.getNumThreads();
		this.NUM_GENERATIONS = frame.getNumGenerations();
		this.exec = Executors.newFixedThreadPool(NUM_THREADS);
		this.barrier = new CyclicBarrier( NUM_THREADS, 
			new Runnable()
			{
				public void run()
				{
					grid.updateGrid( destArray );	// update grid, switch
					ToroidalArray temp = srcArray;
					srcArray = destArray;
					destArray = temp;
				}
			});
		int chunkSize = ((this.xMax*this.yMax) / NUM_THREADS); // divide grid
		int start = 0;
		int stop = (start+chunkSize);
		this.workers = new Worker[NUM_THREADS];
		for(int i = 0; i < NUM_THREADS; ++i)
		{
			// parcel the grid into chunks for thet threads
			int test = (this.xMax*this.yMax)%stop;
			if( test != 0 && test < stop ) // end of set
			{
				stop = (start+test);
			}
			workers[i] = new Worker(start, stop, NUM_GENERATIONS);
			start = stop;
			stop = (start+chunkSize);
		}
	}
	
	//=========================================================================
	// runCyclicBarrier() -- starts the Cyclic Barrier via the Executor
	
	private void runCyclicBarrier()
	{
		if( exec == null || barrier == null )
			throw new IllegalStateException( "Barrier not initialized!" );
		for(int i = 0; i < workers.length; ++i)
		{
			exec.execute( workers[i] ); // kick off the threads
		}
	}
	
	//=========================================================================
	// class Worker -- defines the Thread behavior
	
	private class Worker implements Runnable
	{
		private int start;
		private int stop;
		private int iterations;
		
		public Worker(int start, int stop, int iterations)
		{
			this.start = start;
			this.stop = stop;
			this.iterations = iterations;
		}
		public void run()
		{
			if( iterations > 0 )
			{
				runForNumIterations(); // run for specified generations
			}
			else 
			{
				runForever(); // run infinitely
			}
		}
		public void runForever()
		{
			while( !Thread.interrupted() )
			{
				try
				{
					// do our chunk
					traverse( srcArray, start, stop );
					barrier.await();
				}
				catch ( InterruptedException e )
				{
					Thread.currentThread().interrupt();
				}
				catch ( BrokenBarrierException e )
				{
					return;
				}
			}
		}
		public void runForNumIterations()
		{
			while( !Thread.interrupted() && iterations > 0 )
			{
				try
				{
					// do our chunk
					traverse( srcArray, start, stop );
					iterations -= 1;
					barrier.await();
				}
				catch ( InterruptedException e )
				{
					Thread.currentThread().interrupt();
				}
				catch ( BrokenBarrierException e )
				{
					return;
				}
			}
		}
	}
	
	//=========================================================================
	// traverse() -- go through the sub-section
	
	private void traverse( ToroidalArray arr, int start, int stop )
	{
		for( int i = start; i < stop; ++i ) // x values (row)
		{
			for( int j = 0; j < this.yMax; ++j ) // y values (col) -- immutable
			{
				boolean alive = ( arr.get(i,j) == GREEN || arr.get(i,j) == BLUE );
				int liveCount = getLiveNeighborCount( arr, i, j );
				
				if( alive ) 		
				{
					if( liveCount < 2 ) 			// dies of loneliness
					{
						destArray.set(i, j, RED); 
					}
					else if( liveCount > 3 )		// dies of over-crowding
					{
						destArray.set(i, j, RED); 
					}
				}
				else if( !alive && liveCount == 3) 	// springs to life
				{
					destArray.set(i, j, GREEN); 
				}
				else // migrate the colors
				{
					if( alive && arr.get(i,j) == GREEN )
					{
						destArray.set(i, j, BLUE);
					}
					else if( !alive && arr.get(i,j) == RED )
					{
						destArray.set(i, j, WHITE);
					}
					else // stay the same
					{
						destArray.set(i, j, arr.get(i,j));
					}
				}
			}
		}
	}
	
	//=========================================================================
	// getLiveNeighborCount -- check the live neighbor adjacency
	
	private int getLiveNeighborCount( ToroidalArray arr, int x, int y )
	{
		int liveCount = 0;
		
		// figure out the adjacency of live cells around this point
		liveCount += inspectNeighbor( arr, (x-1), (y) ); // North
		liveCount += inspectNeighbor( arr, (x+1), (y) ); // South
		liveCount += inspectNeighbor( arr, (x), (y+1) ); // East
		liveCount += inspectNeighbor( arr, (x), (y-1) ); // West
		
		liveCount += inspectNeighbor( arr, (x-1), (y-1) ); // NorthWest
		liveCount += inspectNeighbor( arr, (x-1), (y+1) ); // NorthEast
		liveCount += inspectNeighbor( arr, (x+1), (y-1) ); // SouthWest
		liveCount += inspectNeighbor( arr, (x+1), (y+1) ); // SouthEast
		
		return liveCount;
	}
	
	//=========================================================================
	// inspectNeighbor() -- check the cells around here
	
	private int inspectNeighbor( ToroidalArray arr, int x, int y )
	{
		int retVal = 0;
		if( x < 0 || x == this.xMax )
		{
			x = (this.xMax - Math.abs(x));
		}
		if( y < 0 || y == this.yMax )
		{
			y = (this.yMax - Math.abs(y));
		}
		if( arr.get(x,y) == GREEN || arr.get(x,y) == BLUE ) // if alive
		{
			retVal += 1;
		}
		return retVal;
	}
}
